using DotPowerJob.Enum;
using DotPowerJob.Models;
using DotPowerJob.Requests;
using DotPowerJob.Responses;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace DotPowerJob.Queue
{
    public class JobTaskQueue : IDisposable
    {
        private readonly ILogger _logger;
        private readonly ConcurrentQueue<InstanceTask> TASK_QUEUE = new ConcurrentQueue<InstanceTask>();
        private readonly ConcurrentDictionary<long, InstanceTask> ID_IN_QUEUE = new ConcurrentDictionary<long, InstanceTask>();
        private CancellationTokenSource _cancellationTokenSource;
        private Task _runTask;
        private readonly PowerJobConfig _jobConfig;
        public JobTaskQueue(ITaskExecutor executor, ILogger logger, PowerJobConfig jobConfig)
        {
            this.Executor = executor;
            this._logger = logger;
            _jobConfig = jobConfig;
        }

        public ITaskExecutor Executor { get; }

        public bool IsRunning()
        {
            return _cancellationTokenSource != null;
        }

        public PowerJobResponse Push(ServerScheduleJobRequest triggerParam)
        {
            var instanceTask = new InstanceTask(this._jobConfig, this._logger)
            {
                request = triggerParam,
                Status = Enum.InstanceStatus.RUNNING
            };
            if (!ID_IN_QUEUE.TryAdd(triggerParam.InstanceId, instanceTask))
            {
                _logger.LogWarning("repeat job task,logId={logId},jobId={jobId}", triggerParam.InstanceId, triggerParam.JobId);
                return PowerJobResponse.Failed("repeat job task!");
            }

            //this._logger.LogWarning("add job with logId={logId},jobId={jobId}",triggerParam.LogId,triggerParam.JobId);

            this.TASK_QUEUE.Enqueue(instanceTask);
            StartTask();
            return PowerJobResponse.Successed();
        }

        /// <summary>
        /// 停止整个job
        /// </summary>
        public void Stop()
        {
            _cancellationTokenSource?.Cancel();
            _cancellationTokenSource?.Dispose();
            _cancellationTokenSource = null;

            //wait for task completed
            _runTask?.GetAwaiter().GetResult();
        }

        /// <summary>
        /// 停止单个实例
        /// </summary>
        public async void StopInstance(long instanceId)
        {
            if (ID_IN_QUEUE.TryRemove(instanceId, out var instanceTask))
            {
                await instanceTask.StopTask();
            }
        }

        public void Dispose()
        {
            while (!TASK_QUEUE.IsEmpty)
            {
                TASK_QUEUE.TryDequeue(out _);
            }
            ID_IN_QUEUE.Clear();
            Stop();
        }

        private void StartTask()
        {
            if (_cancellationTokenSource != null)
            {
                return; //running
            }
            _cancellationTokenSource = new CancellationTokenSource();
            var ct = _cancellationTokenSource.Token;

            _runTask = Task.Factory.StartNew(async () =>
            {

                //ct.ThrowIfCancellationRequested();
                while (!ct.IsCancellationRequested)
                {
                    if (TASK_QUEUE.IsEmpty)
                    {
                        //_logger.LogInformation("task queue is empty!");
                        break;
                    }

                    PowerJobResponse result = null;
                    InstanceTask instance = null;
                    InstanceStatus status = default;
                    try
                    {

                        if (TASK_QUEUE.TryDequeue(out instance))
                        {
                            if (!ID_IN_QUEUE.TryGetValue(instance.request.InstanceId, out _))
                            {
                                _logger.LogWarning("remove queue failed,logId={logId},jobId={jobId},exists={exists}"
                                    , instance.request.InstanceId, instance.request.JobId, ID_IN_QUEUE.ContainsKey(instance.request.InstanceId));
                            }
                            this._logger.LogInformation("<br>----------- powerjob job execute start -----------<br>----------- Param:{0}", instance.request.InstanceParams);

                            var jobQueueExectorToken = ct;
                            CancellationTokenSource timeoutCts = null;
                            if (instance.request.InstanceTimeoutMS > 0)
                            {
                                timeoutCts = new CancellationTokenSource(instance.request.InstanceTimeoutMS / 1000);
                                instance.ExectorToken = CancellationTokenSource.CreateLinkedTokenSource(instance.ExectorToken.Token, jobQueueExectorToken, timeoutCts.Token);
                            }
                            result = await Executor.Execute(instance.request, instance.ExectorToken.Token);
                            if (timeoutCts != null && timeoutCts.IsCancellationRequested)
                            {
                                result = PowerJobResponse.Failed("time out");
                                timeoutCts.Dispose();
                                timeoutCts = null;
                                status = InstanceStatus.FAILED;
                            }
                            else if (instance.ExectorToken.IsCancellationRequested)
                            {
                                result = PowerJobResponse.Failed("cancel job");
                                status = InstanceStatus.STOPPED;
                            }
                            else
                            {
                                status = InstanceStatus.SUCCEED;
                            }
                            ID_IN_QUEUE.TryRemove(instance.request.InstanceId, out _);
                            this._logger.LogInformation("<br>----------- powerjob job execute end(finish) -----------<br>----------- ReturnT:" + result.Success);
                        }
                        else
                        {
                            _logger.LogWarning("Dequeue Task Failed");
                            status = InstanceStatus.FAILED;
                        }
                    }
                    catch (Exception ex)
                    {
                        result = PowerJobResponse.Failed("Dequeue Task Failed:" + ex.Message);
                        this._logger.LogInformation("<br>----------- JobThread Exception:" + ex.Message + "<br>----------- powerjob job execute end(error) -----------");
                        status = InstanceStatus.FAILED;
                    }

                    if (instance != null)
                    {
                        await instance.ComplateTask(status);
                    }

                }
                _cancellationTokenSource?.Dispose();
                _cancellationTokenSource = null;
            }, _cancellationTokenSource.Token);
        }
    }
}